/*
 * 
 *	Copyright (C) 2011 -- Alfred Reibenschuh
 *
 * 	This program is free software; you can redistribute it and/or 
 *	modify it under the terms of the GNU General Public License as 
 *	published by the Free Software Foundation; either version 2 of 
 *	the License, or (at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful, 
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 *	GNU General Public License for more details.
 * 
 */

package redoak.php.ldap;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;

import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;

import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.env.ArrayValue;
import com.caucho.quercus.env.ArrayValueImpl;
import com.caucho.quercus.env.BooleanValue;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.LongValue;
import com.caucho.quercus.env.StringValue;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.util.L10N;

public abstract class AbstractLdapModule extends AbstractQuercusModule 
{
	
	private static final Logger log = Logger.getLogger(AbstractLdapModule.class.getName());
	private static final L10N L = new L10N(AbstractLdapModule.class);

	public String []getLoadedExtensions()
	{
		return new String[] { "ldap" };
	}
	
	/*
	  	LDAP_DEREF_NEVER (integer)
		LDAP_DEREF_SEARCHING (integer)
		LDAP_DEREF_FINDING (integer)
		LDAP_DEREF_ALWAYS (integer)
		LDAP_OPT_DEREF (integer)
		LDAP_OPT_SIZELIMIT (integer)
		LDAP_OPT_TIMELIMIT (integer)
		LDAP_OPT_NETWORK_TIMEOUT (integer)
		    Option for ldap_set_option() to allow setting network timeout. (Available as of PHP 5.3.0) 
		LDAP_OPT_PROTOCOL_VERSION (integer)
		LDAP_OPT_ERROR_NUMBER (integer)
		LDAP_OPT_REFERRALS (integer)
		LDAP_OPT_RESTART (integer)
		LDAP_OPT_HOST_NAME (integer)
		LDAP_OPT_ERROR_STRING (integer)
		LDAP_OPT_MATCHED_DN (integer)
		LDAP_OPT_SERVER_CONTROLS (integer)
		LDAP_OPT_CLIENT_CONTROLS (integer)
		LDAP_OPT_DEBUG_LEVEL (integer)
		GSLC_SSL_NO_AUTH (integer)
		GSLC_SSL_ONEWAY_AUTH (integer)
		GSLC_SSL_TWOWAY_AUTH (integer) 
	*/

	/*
	    * ldap_8859_to_t61 � Translate 8859 characters to t61 characters
	    * ldap_compare � Compare value of attribute found in entry specified with DN
	    * ldap_err2str � Convert LDAP error number into string error message
	    * ldap_errno � Return the LDAP error number of the last LDAP command
	    * ldap_error � Return the LDAP error message of the last LDAP command
	    * ldap_first_attribute � Return first attribute
	    * ldap_first_reference � Return first reference
	    * ldap_get_option � Get the current value for given option
	    * ldap_get_values_len � Get all binary values from a result entry
	    * ldap_mod_add � Add attribute values to current attributes
	    * ldap_mod_del � Delete attribute values from current attributes
	    * ldap_mod_replace � Replace attribute values with new ones
	    * ldap_next_attribute � Get the next attribute in result
	    * ldap_next_reference � Get next reference
	    * ldap_parse_reference � Extract information from reference entry
	    * ldap_parse_result � Extract information from result
	    * ldap_rename � Modify the name of an entry
	    * ldap_set_option � Set the value of the given option
	    * ldap_set_rebind_proc � Set a callback function to do re-binds on referral chasing
	    * ldap_sort � Sort LDAP result entries
	    * ldap_start_tls � Start TLS
	    * ldap_t61_to_8859 � Translate t61 characters to 8859 characters
    */
	
	//resource ldap_connect  ([ string $hostname = NULL  [, int $port = 389  ]] )
	public abstract Value ldap_connect(Env env, @Optional String hosturi, @Optional Integer port);
	
	//bool ldap_bind  ( resource $link_identifier  [, string $bind_rdn = NULL  [, string $bind_password = NULL  ]] )
	public Value ldap_bind(Env env, Value pldap, @Optional String dn, @Optional String cred)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		
		if(dn==null || dn.equals(""))
		{
			return ldap.bind() ? BooleanValue.TRUE : BooleanValue.FALSE;
		}
		else
		{
			return ldap.bindSimple(dn, cred) ? BooleanValue.TRUE : BooleanValue.FALSE;
		}
	}
	
	//EXTERNAL 	  	  	
	//GSSAPI (Kerberos v5) 	  	  	
	//DIGEST-MD5 	  	  	
	//CRAM-MD5 	  	  	
	//PLAIN 	  	  	  	  	  	
	//ANONYMOUS
	//bool ldap_sasl_bind  ( resource $link  [, string $binddn = NULL  [, string $password = NULL  [, string $sasl_mech = NULL  [, string $sasl_realm = NULL  [, string $sasl_authc_id = NULL  [, string $sasl_authz_id = NULL  [, string $props = NULL  ]]]]]]] )
	public Value ldap_sasl_bind(Env env, Value pldap, @Optional String dn, @Optional String cred, @Optional String sasl_mech)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		
		if(dn==null || dn.equals(""))
		{
			return ldap.bindSasl(null, null, "ANONYMOUS") ? BooleanValue.TRUE : BooleanValue.FALSE;
		}
		else
		{
			return ldap.bindSasl(dn, cred, sasl_mech) ? BooleanValue.TRUE : BooleanValue.FALSE;
		}
	}
	
	//bool ldap_close  ( resource $link_identifier  )
	public Value ldap_close(Env env, Value pldap)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		return ldap.close() ? BooleanValue.TRUE : BooleanValue.FALSE;
	}

	//bool ldap_unbind  ( resource $link_identifier  )
	public Value ldap_unbind(Env env, Value pldap)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		return ldap.close() ? BooleanValue.TRUE : BooleanValue.FALSE;
	}
	
	//resource ldap_read  ( resource $link_identifier  , string $base_dn  , string $filter  [, array $attributes  [, int $attrsonly  [, int $sizelimit  [, int $timelimit  [, int $deref  ]]]]] )
	public Value ldap_read(Env env, Value pldap, String dn, String filter)
	{
		Object obj = null;
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		if(filter==null || filter.equals(""))
		{
			obj = ldap.get(dn);
		}
		else
		{
			obj = ldap.get(dn, filter);
		}
		return env.wrapJava(obj, true);
	}
	
	//resource ldap_get  ( resource $link_identifier  , string $dn [, int $timelimit  [, int $deref  ]] )
	public Value ldap_get(Env env, Value pldap, String dn)
	{
		Object obj = null;
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		obj = ldap.get(dn);
		return env.wrapJava(obj, true);
	}
	
	//resource ldap_list  ( resource $link_identifier  , string $base_dn  , string $filter  [, array $attributes  [, int $attrsonly  [, int $sizelimit  [, int $timelimit  [, int $deref  ]]]]] )
	public Value ldap_list(Env env, Value pldap, String dn, String filter)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		return env.wrapJava(ldap.list(dn, filter), true);
	}
	
	//resource ldap_search  ( resource $link_identifier  , string $base_dn  , string $filter  [, array $attributes  [, int $attrsonly  [, int $sizelimit  [, int $timelimit  [, int $deref  ]]]]] )
	public Value ldap_search(Env env, Value pldap, String dn, String filter)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		return env.wrapJava(ldap.search(dn, filter), true);
	}
		
	//resource ldap_first_entry  ( resource $link_identifier  , resource $result_identifier  )
	public Value ldap_first_entry(Env env, Value pldap, Value presult)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		Object result = presult.toJavaObject();
		if(result instanceof Vector)
		{
			Enumeration en = ((Vector)result).elements();
			return env.wrapJava(new Object[]{ en, en.nextElement() }, true);
		}
		else
		{
			return env.wrapJava(result, true);
		}
	}
	
	//resource ldap_next_entry  ( resource $link_identifier  , resource $result_entry_identifier  )
	public Value ldap_next_entry(Env env, Value pldap, Value pentry)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		Object o_entry = pentry.toJavaObject();
		if(o_entry instanceof Object[])
		{
			Object[] entry = (Object[])pentry.toJavaObject();
			if(((Enumeration)entry[0]).hasMoreElements())
			{
				return env.wrapJava(new Object[]{ entry[0], ((Enumeration)entry[0]).nextElement() }, true);
			}
		}
		return BooleanValue.FALSE;
	}
	
	//bool ldap_free_result  ( resource $result_identifier  )
	public Value ldap_free_result(Env env, Value presult)
	{
		Object result = presult.toJavaObject();
		if(result instanceof Vector)
		{
			((Vector)result).clear();
			return BooleanValue.TRUE;
		}
		return BooleanValue.FALSE;
	}
	
	//bool ldap_delete  ( resource $link_identifier  , string $dn  )
	public Value ldap_delete(Env env, Value pldap, String dn)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		if(ldap.delete(dn))
		{
			return BooleanValue.TRUE;
		}
		return BooleanValue.FALSE;
	}		
	
	private Value toValues(String[] values)
	{
		if(values!=null)
		{
			ArrayValue av = new ArrayValueImpl();
			for(String v : values)
			{
				av.put(v);
			}
			av.put("count", values.length);
			return av;
		}
		return BooleanValue.FALSE;
	}
	
	private Value ht2attr(Env env, NamedHashtable ht)
	{
		ArrayValue av = new ArrayValueImpl();
		for(Object k : ht.keySet().toArray())
		{
			av.put(k.toString());
			av.put(env.createString(k.toString()), toValues((String[])ht.get(k)));
		}
		av.put("count", ht.keySet().toArray().length);
		av.put("dn", ht.getName());
		return av;
	}
	
	//array ldap_get_attributes  ( resource $link_identifier  , resource $result_entry_identifier  )
	public Value ldap_get_attributes(Env env, Value pldap, Value pres)
	{
		Object obj = pres.toJavaObject();
		if(obj instanceof NamedHashtable)
		{
			return ht2attr(env, (NamedHashtable)obj);
		}
		else if(obj instanceof Object[])
		{
			return ht2attr(env, (NamedHashtable)((Object[])obj)[1]);
		}
		return BooleanValue.FALSE;
	}		
	
	//int ldap_count_entries  ( resource $link_identifier  , resource $result_identifier  )
	public Value ldap_count_entries(Env env, Value pldap, Value presult)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		Object result = presult.toJavaObject();
		if(result instanceof Vector)
		{
			return new LongValue(((Vector)result).size());
		}
		else if(result instanceof Hashtable)
		{
			return new LongValue(1L);
		}
		return BooleanValue.FALSE;
	}

	// array ldap_get_entries  ( resource $link_identifier  , resource $result_identifier  )
	public Value ldap_get_entries(Env env, Value pldap, Value presult)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		Object result = presult.toJavaObject();
		if(result instanceof Vector)
		{
			ArrayValue av = new ArrayValueImpl();
			for(NamedHashtable o : (Vector<NamedHashtable>)result)
			{
				av.put(ht2attr(env, o));
			}
			av.put("count", ((Vector)result).size());
			return av;
		}
		else if(result instanceof NamedHashtable)
		{
			ArrayValue av = new ArrayValueImpl();
			av.put(ht2attr(env, (NamedHashtable)result));
			av.put("count", 1);
			return av;
		}
		return BooleanValue.FALSE;
	}
	
	// string ldap_get_dn  ( resource $link_identifier  , resource $result_entry_identifier  )
	public Value ldap_get_dn(Env env, Value pldap, Value pres)
	{
		Object obj = pres.toJavaObject();
		if(obj instanceof NamedHashtable)
		{
			return env.createString(((NamedHashtable)obj).getName());
		}
		else if(obj instanceof Object[])
		{
			return env.createString(((NamedHashtable)((Object[])obj)[1]).getName());
		}
		return BooleanValue.FALSE;
	}		
	
	//array ldap_get_values  ( resource $link_identifier  , resource $result_entry_identifier  , string $attribute  )
	public Value ldap_get_values(Env env, Value pldap, Value pres, String name)
	{
		Object obj = pres.toJavaObject();
		if(obj instanceof NamedHashtable)
		{
			return toValues(((NamedHashtable)obj).get(name));
		}
		else if(obj instanceof Object[])
		{
			return toValues(((NamedHashtable)((Object[])obj)[1]).get(name));
		}
		return BooleanValue.FALSE;
	}		
	
	//array ldap_explode_dn  ( string $dn  , int $with_attrib  )
	public Value ldap_explode_dn(Env env, String dn, Integer with_attrib)
	{
		try
		{
			LdapName name = new LdapName(dn);
			ArrayValue av = new ArrayValueImpl();
			if(with_attrib==1)
			{
				Iterator<Rdn> en = name.getRdns().iterator();
				while(en.hasNext())
				{
					av.put(en.next().toString());
				}
			}
			else
			{
				Enumeration<String> en = name.getAll();
				while(en.hasMoreElements())
				{
					av.put(en.nextElement());
				}
			}
			av.put("count", name.size());
			return av;
		}
		catch(Exception ex) {}
		return BooleanValue.FALSE;
	}		
	
	//string ldap_dn2ufn  ( string $dn  )
	public Value ldap_dn2ufn(Env env, String dn)
	{
		try
		{
			LdapName name = new LdapName(dn);
			if(name.size()==0)
			{
				return BooleanValue.FALSE;
			}
			else
			{
				StringValue sb = env.createStringBuilder();
				Enumeration<String> en = name.getAll();
				sb.append(en.nextElement());
				while(en.hasMoreElements())
				{
					sb.append(","+en.nextElement());
				}
				return sb;
			}
		}
		catch(Exception ex) {}
		return BooleanValue.FALSE;
	}		

	private NamedHashtable toHash(Env env, ArrayValue entry)
	{
		NamedHashtable ht = new NamedHashtable();
		long count = entry.get(env.createString("count")).toLong();
		for(long i=0; i<count; i++)
		{
			String k = entry.get(new LongValue(i)).toJavaString();
			Vector<String> vs = new Vector();
			for(Value v : entry.get(env.createString(k)).toArrayValue(env).valuesToArray())
			{
				vs.add(v.toJavaString());
			}
			ht.put(k, vs.toArray(new String[]{}));
		}
		return ht;
	}
	
	//bool ldap_add  ( resource $link_identifier  , string $dn  , array $entry  )
	public Value ldap_add(Env env, Value pldap, String dn, ArrayValue pentry)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		NamedHashtable ht = toHash(env, pentry);
		ht.setName(dn);
		if(ldap.add(ht))
		{
			return BooleanValue.TRUE;
		}
		return BooleanValue.FALSE;
	}
	
	//bool ldap_modify  ( resource $link_identifier  , string $dn  , array $entry  )
	public Value ldap_modify(Env env, Value pldap, String dn, ArrayValue pentry)
	{
		ILdapSession ldap = (ILdapSession)pldap.toJavaObject();
		NamedHashtable ht = toHash(env, pentry);
		ht.setName(dn);
		if(ldap.modify(ht))
		{
			return BooleanValue.TRUE;
		}
		return BooleanValue.FALSE;
	}
	

}
